﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace ChatServer
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together.
    // NOTE: In order to launch WCF Test Client for testing this service, please select Service1.svc or Service1.svc.cs at the Solution Explorer and start debugging.
    [ServiceBehavior(ConcurrencyMode= ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.Single)]
    public class Server : IServer
    {
        object _syncRoot = new object();
        Dictionary<string, string> _sessionIdTopic = new Dictionary<string, string>();
        Dictionary<string, Dictionary<string, INotification>> _topicSessionIdCallbackChannel = new Dictionary<string, Dictionary<string, INotification>>();
        static AsyncCallback onRecieveCompleted = new AsyncCallback(RecieveCompleted);

        public void Subscribe(string topic)
        {
            topic = topic != null ? topic : string.Empty;
            string sessionId = OperationContext.Current.Channel.SessionId;
            INotification callBackChannel = OperationContext.Current.GetCallbackChannel<INotification>();
            lock (_syncRoot)
            {
                this._sessionIdTopic[sessionId] = topic;
                Dictionary<string, INotification> sessionIdCallbackChannel = null;
                if (!this._topicSessionIdCallbackChannel.TryGetValue(topic,out sessionIdCallbackChannel))
                {
                    sessionIdCallbackChannel = new Dictionary<string, INotification>();
                    this._topicSessionIdCallbackChannel[topic] = sessionIdCallbackChannel;                    
                }
                sessionIdCallbackChannel[sessionId] = callBackChannel;
                
            }
            OperationContext.Current.Channel.Faulted += new EventHandler(this.Unsubscribe);
            OperationContext.Current.Channel.Closed += new EventHandler(this.Unsubscribe);
        }

        private void Publish(string topic, object content)
        {
            topic = topic != null ? topic : string.Empty;
            List<INotification> clientsToNotify = null;
            lock (this._syncRoot)
            {
                Dictionary<string, INotification> sessionIdCallBackChannel = null;
                if (this._topicSessionIdCallbackChannel.TryGetValue(topic,out sessionIdCallBackChannel))
                {
                    clientsToNotify = new List<INotification>(
                        sessionIdCallBackChannel.Where(x => x.Key != OperationContext.Current.Channel.SessionId).Select(x => x.Value)
                        );                    
                }
            }
            if (clientsToNotify!=null && clientsToNotify.Count>0)
            {
                foreach (INotification callbackChannel in clientsToNotify)
                {
                    try
                    {
                        if (content is string)
                        {
                            callbackChannel.BeginRecieveText(content as string, onRecieveCompleted, new object[] { callbackChannel, typeof(string) });
                        } else if(content is VideoFrame)
                        {
                            callbackChannel.BeginRecieveVideo(content as VideoFrame, onRecieveCompleted, new object[] { callbackChannel, typeof(VideoFrame) });
                        }
                    }
                    catch (Exception ex)
                    {
                    }
                }
            }
        }
        private static void RecieveCompleted(IAsyncResult result)
        {
            object[] state = (object[])result.AsyncState;
            try
            {
                if (state[1]  == typeof(string))
                {
                    ((INotification)state[0]).EndRecieveText(result);
                }
                else if (state[1] == typeof(VideoFrame))
                {
                    ((INotification)state[0]).EndRecieveVideo(result);
                }
            }
            catch (Exception ex)
            {
            }
        }
        public void PublishText(string topic, string content)
        {
            Publish(topic, content);
        }

        public void PublishVideo(string topic, VideoFrame content)
        {
            Publish(topic, content);
        }

        void Unsubscribe(object sender, EventArgs e)
        {
            IContextChannel channel = (IContextChannel)sender;
            if (channel!=null && channel.SessionId!=null)
            {
                lock (this._syncRoot)
                {
                    string topic = null;
                    if (this._sessionIdTopic.TryGetValue(channel.SessionId,out topic))
                    {
                        this._sessionIdTopic.Remove(channel.SessionId);
                        Dictionary<string, INotification> sessionIdCallbackChannel = null;
                        if(this._topicSessionIdCallbackChannel.TryGetValue(topic,out sessionIdCallbackChannel))
                        {
                            sessionIdCallbackChannel.Remove(channel.SessionId);
                            if(sessionIdCallbackChannel.Count==0)
                            {
                                this._topicSessionIdCallbackChannel.Remove(topic);
                            }
                        }
                    }
                }
            }
        }
    }
}
